/*
 * Open Source RFID Access Controller
 *
 * 4/3/2011 v1.32
 * Last build test with Arduino v00.21
 * Arclight - arclight@23.org
 * Danozano - danozano@gmail.com
 *
 * Notice: This is free software and is probably buggy. Use it at
 * at your own peril.  Use of this software may result in your
 * doors being left open, your stuff going missing, or buggery by
 * high seas pirates. No warranties are expressed on implied.
 * You are warned.
 *
 *
 * For latest downloads, including Eagle CAD files for the hardware, check out
 * http://code.google.com/p/open-access-control/downloads/list
 *
 * Latest update moves strings to PROGMEM to free up memory and adds a 
 * console password feature.
 * 
 *
 * This program interfaces the Arduino to RFID, PIN pad and all
 * other input devices using the Wiegand-26 Communications
 * Protocol. It is recommended that the keypad inputs be
 * opto-isolated in case a malicious user shorts out the 
 * input device.
 * Outputs go to a Darlington relay driver array for door hardware/etc control.
 * Analog inputs are used for alarm sensor monitoring.  These should be
 * isolated as well, since many sensors use +12V. Note that resistors of
 * different values can be used on each zone to detect shorting of the sensor
 * or wiring.
 *
 * Version 1.00+ of the hardware implements these features and uses the following pin 
 * assignments on a standard Arduino Duemilanova or Uno:
 *
 * Relay outpus on digital pins 6,7,8,9
 * DS1307 Real Time Clock (I2C):A4 (SDA), A5 (SCL)
 * Analog pins (for alarm):A0,A1,A2,A3 
 * Reader 1: pins 2,3
 * Reader 2: pins 4,5
 * Ethernet: pins 10,11,12,13 (Not connected to the board, reserved for the Ethernet shield)
 *
 * Quickstart tips: 
 * Set the console password(PRIVPASSWORD) value to a numeric DEC or HEX value.
 * Define the static user list by swiping a tag and copying the value received into the #define values shown below 
 * Compile and upload the code, then log in via serial console at 57600,8,N,1
 *
 */

#include <Wire.h>         // Needed for I2C Connection to the DS1307 date/time chip
#include <EEPROM.h>       // Needed for saving to non-voilatile memory on the Arduino.
#include <avr/pgmspace.h> // Allows data to be stored in FLASH instead of RAM

/*
#include <Ethernet.h>   // Ethernet stuff, comment out if not used.
#include <SPI.h>          
#include <Server.h>
#include <Client.h>
*/

#include <DS1307.h>       // DS1307 RTC Clock/Date/Time chip library
#include <WIEGAND26.h>    // Wiegand 26 reader format libary
#include <PCATTACH.h>     // Pcint.h implementation, allows for >2 software interupts.


/* Static user List - Implemented as an array for testing and access override 
 */                               

#define DEBUG 2                         // Set to 2 for display of raw tag numbers in log files, 1 for only denied, 0 for never.               

#define gonzo   0x1234                  // Name and badge number in HEX. We are not using checksums or site ID, just the whole
#define snake   0x1234                  // output string from the reader.
#define satan   0x1234
//const long  superUserList[] = { gonzo, snake, satan};  // Super user table (cannot be changed by software)

#define PRIVPASSWORD 0x1234             // Console "priveleged mode" password

#define PRIVPASSWORD 99887766
#define testUser1TagNName 5664657    //blue tag:  used in testing as regular user also
// add access code 0x2589 when the user with the blue rfid tag is being added -- used for testing
#define testUser2TagNName 3788191    //yellow tag:  used in testing as regular user also
//   add access code 0x1234 when the user with the yellow rfid tag is being added  --used fortesting
#define testUser3TagNName 4294967295
const unsigned long superUserList[] = { testUser1TagNName, testUser2TagNName, testUser3TagNName } ; //looks like input new users as a super user list

#define DOORDELAY 5000                  // How long to open door lock once access is granted. (2500 = 2.5s)
#define SENSORTHRESHOLD 100             // Analog sensor change that will trigger an alarm (0..255)

#define EEPROM_ALARM 0                  // EEPROM address to store alarm triggered state between reboots (0..511)
#define EEPROM_ALARMARMED 1             // EEPROM address to store alarm armed state between reboots
#define EEPROM_ALARMZONES 20            // Starting address to store "normal" analog values for alarm zone sensor reads.
#define KEYPADTIMEOUT 5000              // Timeout for pin pad entry. Users on keypads can enter commands after reader swipe.


//#define EEPROM_FIRSTUSER 24
//#define EEPROM_LASTUSER 1024
//#define NUMUSERS  ((EEPROM_LASTUSER - EEPROM_FIRSTUSER)/5)  //Define number of internal users (200 for UNO/Duemillanova)

#define EEPROM_FIRSTUSER 25
#define EEPROM_LASTUSER 1024
#define NUMUSERS  ((EEPROM_LASTUSER - EEPROM_FIRSTUSER)/9)  //Define number of internal users (111 for UNO/Duemillanova)

#define DOORPIN1 relayPins[0]           // Define the pin for electrified door 1 hardware
#define DOORPIN2 relayPins[2]           // Define the pin for electrified door 2 hardware
#define ALARMSTROBEPIN relayPins[3]     // Define the "non alarm: output pin. Can go to a strobe, small chime, etc
#define ALARMSIRENPIN  relayPins[1]     // Define the alarm siren pin. This should be a LOUD siren for alarm purposes.

byte reader1Pins[]={2,3};               // Reader 1 connected to pins 4,5
byte reader2Pins[]= {4,5};              // Reader2 connected to pins 6,7

//byte reader3Pins[]= {10,11};                // Reader3 connected to pins X,Y (Not implemented on v1.x and 2.x Access Control Board)

const byte analogsensorPins[] = {0,1,2,3};    // Alarm Sensors connected to other analog pins
const byte relayPins[]= {6,7,8,9};            // Relay output pins

bool door1Locked=true;                        // Keeps track of whether the doors are supposed to be locked right now
bool door2Locked=true;

unsigned long door1locktimer=0;               // Keep track of when door is supposed to be relocked
unsigned long door2locktimer=0;               // after access granted.

boolean doorChime=false;                       // Keep track of when door chime last activated
boolean doorClosed=false;                      // Keep track of when door last closed for exit delay

unsigned long alarmDelay=0;                    // Keep track of alarm delay. Used for "delayed activation" or level 2 alarm.
unsigned long alarmSirenTimer=0;               // Keep track of how long alarm has gone off


unsigned long consolefailTimer=0;               // Console password timer for failed logins
byte consoleFail=0;
#define numUsers (sizeof(superUserList)/sizeof(long))                  //User access array size (used in later loops/etc)
#define NUMDOORS (sizeof(doorPin)/sizeof(byte))
#define numAlarmPins (sizeof(analogsensorPins)/sizeof(byte))

//Other global variables
byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;     // Global RTC clock variables. Can be set using DS1307.getDate function.

byte alarmActivated = EEPROM.read(EEPROM_ALARM);                   // Read the last alarm state as saved in eeprom.
byte alarmArmed = EEPROM.read(EEPROM_ALARMARMED);                  // Alarm level variable (0..5, 0==OFF) 

boolean sensor[4]={false};                                         // Keep track of tripped sensors, do not log again until reset.
unsigned long sensorDelay[2]={0};                                  // Same as above, but sets a timer for 2 of them. Useful for logging
                                                                   // motion detector hits for "occupancy check" functions.

// Enable up to 3 door access readers.
volatile long reader1 = 0;
volatile int  reader1Count = 0;
volatile long reader2 = 0;
volatile int  reader2Count = 0;
int userMask1=0;
int userMask2=0;
boolean keypadGranted=0;     // Variable that is set for authenticated users to use keypad after login

//volatile long reader3 = 0;                                   // Uncomment if using a third reader.
//volatile int  reader3Count = 0;

unsigned long keypadTime = 0;                                  // Timeout counter for  reader with key pad
unsigned long keypadValue=0;


// Serial terminal buffer (needs to be global)
char inString[50]={0};                                         // Size of command buffer (<=128 for Arduino)
byte inCount=0;
//boolean privmodeEnabled = false;    
// Switch for enabling "priveleged" commands
// ML changed to true
boolean privmodeEnabled = true; 

//used for logic in checking both rfid and keypad entry
bool  rfidNumOk = 0 ;  // if rfid tag has been read, this is set to 1 (true)
bool  keyPadNumOk = 0 ;  // if keypad access number has been read this is set to 1 (true) NOTE: on keypad have numbers 0, ...,9

unsigned long digAccessCodeUL =  0 ;   //ubsigned long value for  4 digit access code in long to match the reader2 type
unsigned long rfidTagNum  = 0 ;  //value for the 26 digit max rfid code
/* Create an instance of the various C++ libraries we are using.
 */

DS1307 ds1307;        // RTC Instance
WIEGAND26 wiegand26;  // Wiegand26 (RFID reader serial protocol) library
PCATTACH pcattach;    // Software interrupt library

/* Set up some strings that will live in flash instead of memory. This saves our precious 2k of
 * RAM for something else.
*/

const prog_uchar rebootMessage[]          PROGMEM  = {"Access Control System rebooted."};

const prog_uchar doorChimeMessage[]       PROGMEM  = {"Front Door opened."};
const prog_uchar doorslockedMessage[]     PROGMEM  = {"All Doors relocked"};
const prog_uchar alarmtrainMessage[]      PROGMEM  = {"Alarm Training performed."};
const prog_uchar privsdeniedMessage[]     PROGMEM  = {"Access Denied. Priveleged mode is not enabled."};
const prog_uchar privsenabledMessage[]    PROGMEM  = {"Priveleged mode enabled."};
const prog_uchar privsdisabledMessage[]   PROGMEM  = {"Priveleged mode disabled."};
const prog_uchar privsAttemptsMessage[]   PROGMEM  = {"Too many failed attempts. Try again later."};

const prog_uchar consolehelpMessage1[]    PROGMEM  = {"Valid commands are:"};
const prog_uchar consolehelpMessage2[]    PROGMEM  = {"(d)ate, (s)show user, (m)odify user <num>  <usermask> <tagnumber>"};
const prog_uchar consolehelpMessage3[]    PROGMEM  = {"(a)ll user dump,(r)emove_user <num>,(o)open door <num>"};
const prog_uchar consolehelpMessage4[]    PROGMEM  = {"(u)nlock all doors,(l)lock all doors"};
const prog_uchar consolehelpMessage5[]    PROGMEM  = {"(1)disarm_alarm, (2)arm_alarm,(3)train_alarm (9)show_status"};
const prog_uchar consolehelpMessage6[]    PROGMEM  = {"(e)nable <password> - enable or disable priveleged mode"};   
const prog_uchar consolehelpMessage7I3D[]   PROGMEM  = {"(p)articularsOfUser <num> <usermask> <rfidtagnumber> <userAccessCode>"};
const prog_uchar consolehelpMessage8I3D[]   PROGMEM  = {"(R)emove user <num>, (S)how user <num>, (A)ll user dump"} ;  //for use with i3d extra info
const prog_uchar consoledefaultMessage[]  PROGMEM  = {"Invalid command. Press '?' for help."};

const prog_uchar statusMessage1[]         PROGMEM  = {"Alarm armed state (1=armed):"};
const prog_uchar statusMessage2[]         PROGMEM  = {"Alarm siren state (1=activated):"};
const prog_uchar statusMessage3[]         PROGMEM  = {"Front door open state (0=closed):"};
const prog_uchar statusMessage4[]         PROGMEM  = {"Roll up door open state (0=closed):"};     
const prog_uchar statusMessage5[]         PROGMEM  = {"Door 1 unlocked state(1=locked):"};                   
const prog_uchar statusMessage6[]         PROGMEM  = {"Door 2 unlocked state(1=locked):"}; 




void setup(){           // Runs once at Arduino boot-up


    Wire.begin();   // start Wire library as I2C-Bus Master

  /* Attach pin change interrupt service routines from the Wiegand RFID readers
   */
  pcattach.PCattachInterrupt(reader1Pins[0], callReader1Zero, CHANGE); 
  pcattach.PCattachInterrupt(reader1Pins[1], callReader1One,  CHANGE);  
  pcattach.PCattachInterrupt(reader2Pins[1], callReader2One,  CHANGE);
  pcattach.PCattachInterrupt(reader2Pins[0], callReader2Zero, CHANGE);

  //Clear and initialize readers
  wiegand26.initReaderOne(); //Set up Reader 1 and clear buffers.
  wiegand26.initReaderTwo(); 


  //Initialize output relays

  for(byte i=0; i<4; i++){        
    pinMode(relayPins[i], OUTPUT);                                                      
    digitalWrite(relayPins[i], LOW);                  // Sets the relay outputs to LOW (relays off)
  }


 //ds1307.setDateDs1307(0,37,23,6,25,2,11);         
  /*  Sets the date/time (needed once at commissioning)
   
   byte second,        // 0-59
   byte minute,        // 0-59
   byte hour,          // 1-23
   byte dayOfWeek,     // 1-7
   byte dayOfMonth,    // 1-28/29/30/31
   byte month,         // 1-12
   byte year);          // 0-99
   */



  Serial.begin(57600);	               	       // Set up Serial output at 8,N,1,57600bps
  logReboot();
  //chirpAlarm(1);                               // Chirp the alarm to show system ready.
  chirpAlarm(2) ;
  
//  hardwareTest(100);                         // IO Pin testing routing (use to check your inputs with hi/lo +(5-12V) sources)
                                               // Also checks relays

} //end setup




void loop()                                     // Main branch, runs over and over again
{                         

readCommand();                                 // Check for commands entered at serial console

  
  /* Check if doors are supposed to be locked and lock/unlock them 
   * if needed. Uses global variables that can be set in other functions.
   */

  if(((millis() - door1locktimer) >= DOORDELAY) && (door1locktimer !=0))
  { 
    if(door1Locked==true){
     doorLock(1);
     door1locktimer=0;    }

    else {                        
      doorUnlock(1); 
      door1locktimer=0;
                        }                         
   }

  

 if(((millis() - door2locktimer) >= DOORDELAY) && (door2locktimer !=0))
  { 
    if(door2Locked==true) {
     doorLock(2); 
     door2locktimer=0;
                          }
   
    else {
     doorUnlock(2); 
     door2locktimer=0;
                         }   
  }   

  /*  Set optional "failsafe" time to lock up every night.
  */

  ds1307.getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);   // Get the current date/time

  if(hour==23 && minute==59 && door1Locked==false){
         doorLock(1);
         door1Locked==true;      
         Serial.println("Door 1 locked for 2359 bed time.");
  }

          





  // Notes: RFID polling is interrupt driven, just test for the reader1Count value to climb to the bit length of the key
  // change reader1Count & reader1 et. al. to arrays for loop handling of multiple reader output events
  // later change them for interrupt handling as well!
  // currently hardcoded for a single reader unit

  /* This code checks a reader with a 26-bit keycard input. Use the second routine for readers with keypads.  
   * A 5-second window for commands is opened after each successful key access read.
   */

  if(reader1Count >= 26){                           //  When tag presented to reader1 (No keypad on this reader)
    logTagPresent(reader1,1);                       //  write log entry to serial port

/* Check a user's security level and take action as needed. The
*  usermask is a variable from 0..255. By default, 0 and 255 are for
*  locked out users or uninitialized records.
*  Modify these for each door as needed.
*/

  userMask1=checkUser(reader1);    

  if(userMask1>=0) {    
    
   switch(userMask1) {

   case 0:                                      // No outside privs, do not log denied.
    {                                           // authenticate only.
    logAccessGranted(reader1, 1);
    break;
    }

   case 20:                                                // Example Limited hours user
    {                                                      // Can enter from 5:00pm to 11:00pm
    ds1307.getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);    
    if((hour >=17) && (hour <=23)){
         logAccessGranted(reader1, 1);                    // Log and unlock door 2
         alarmState(0);
         armAlarm(0);                                     //  Deactivate Alarm
      //   chirpAlarm(1);                            
         door1locktimer=millis();
         doorUnlock(1);                                   // Unlock the door.
    }
     break;
    }      

   case 255:                                              // Locked out user     
    {
     Serial.print("User ");
     Serial.print(userMask1,DEC);
     Serial.println(" locked out.");
     break;
    }
   
   default:  
    {            
         logAccessGranted(reader1, 1);           // Log and unlock door 1
         alarmState(0);
         armAlarm(0);                            //  Deactivate Alarm                  
         door1locktimer=millis();
         doorUnlock(1);                          // Unlock the door.
         break;
    }
                       }                                      

  }
    else 
    {                                           
    if(checkSuperuser(reader1) >= 0) {              // Check if a superuser, grant access.
      logAccessGranted(reader1, 1);                 // Log and unlock door 1
         alarmState(0);
         armAlarm(0);                               //  Deactivate Alarm
         door1locktimer=millis();
         doorUnlock(1);                             // Unlock the door.
                                      }
      else{                                
       logAccessDenied(reader1,1);                   // No tickee, no laundree
          }
    }

    wiegand26.initReaderOne();                     // Reset for next tag scan

  }

//READER 2 used for case of Keypad
// USAGE AT I3D:  KEYPAD STRICTLY FOR 4 number access pin 
//  SO 2 level access; rfid and 4 code pin both needed
  
  //make sure tag and access code are zeroed out to start each pass through loop for reader 2
   rfidNumOk = 0 ;
   rfidTagNum = 0;
   digAccessCodeUL = 0 ;
   keyPadNumOk = 0 ;
    
  if(reader2Count >= 26){                                // Tag presented to reader 2
    logTagPresent(reader2,2);                            // Write log entry to serial port
    chirpAlarm(1)  ;                                    // Chirp alarm to show that tag input done              
                                                         // CHECK TAG IN OUR LIST OF USERS. -1 = no match                                  
    keypadGranted=0;                                   // Reset the keypad authorized variable
    rfidNumOk = 1 ;       //indicates that reader2 has read the rfid tag
    rfidTagNum = reader2 ;
    Serial.println(" The rfid tag num from reader 2 " ) ;
    Serial.println( reader2 );
    Serial.println(" The rfid tag num " ) ;
    Serial.println( rfidTagNum,DEC );
    Serial.println(" The rfid Num OK " ) ;
    Serial.println( rfidNumOk,DEC );
    wiegand26.initReaderTwo();  //init before reading the keypad
  }
   //Serial.println(" rfidNumOk is ") ;
   //Serial.println( rfidNumOk,DEC );
   delay(10) ;  //try a delay in number milliseconds before going on
  
   //Preparation before access code 
   // keypadGranted = 1 ;
    keypadValue = 0 ;
    keypadTime=millis();      
    
    //Serial.println("input keypad num now") ;
  //here  do read from the keypad for the access code assumed to be 4 digits and ent press
  
 
 while ( (rfidNumOk == 1) &&  ((millis() - keypadTime)  <=  KEYPADTIMEOUT) )
  {                                        // If rfid read,  open  window for pin pad commands.
     if(reader2Count >= 4 )  //QUESTION: is reader2count is getting reset after each press
      { 
          // DESIGN: Pin pad access code is 4, terminated with 'Enter' on the keypad.
          Serial.println(" reader2Count") ;  //IS GETTING INSIDE
          Serial.println(reader2Count) ;
          if(reader2 !=0xB) 
          {  
            if(keypadValue ==0)
            {                              // This 0..9, A..F encoding works with many Wiegand-format keypad or reader 
              keypadValue = reader2;              // plus keypad units.
               //Serial.println(keypadValue,HEX) ;
          }
            else if(keypadValue !=0) {
              keypadValue = keypadValue <<4;
              keypadValue |= reader2; 
              Serial.println("keypadValue hex ") ;
              Serial.println(keypadValue, HEX); 
             // Serial.println("keypadValue dec ") ;
             //Serial.println(keypadValue, DEC); 
              digAccessCodeUL = keypadValue ;
              Serial.println("dig access code UL printed in hex is") ;
              Serial.println(digAccessCodeUL,HEX) ;
              keyPadNumOk = 1 ;
            }
            wiegand26.initReaderTwo();   //Reset reader two and move on. 
          } 
          else 
         {   
            //wiegand26.initReaderTwo(); 
           break;
         }
       }  
   }
   keypadGranted = 0 ;
  
  
 //after the input commands, print out what the rfid and 4 digit access codes are
 // Serial.println(" after the rfid and keypad access code bloc");
 // Serial.println(" keyPadNumOk: ") ;
 // Serial.println(keyPadNumOk);
  if (keyPadNumOk == 1 ){
    chirpAlarm(1) ;  // this is where the chirp is for the access code input ok -- do not want it in the 5 second time out
     Serial.println( " the dig access code UL printed in hex is is" );
     Serial.println( digAccessCodeUL,HEX );
     Serial.println(" the dig access code UL printed in UL") ;
     Serial.println( digAccessCodeUL) ;
  }
  if (rfidNumOk == 1) {
   Serial.println(" The 2nd printout of the rfid tag num " ) ;
   Serial.println( rfidTagNum,DEC );
  }
  
if (rfidNumOk  == 1 && keyPadNumOk == 1 ) 
{
  //userMask2=checkUser(reader2);  // now the keypad is also using reader 2
  //userMask2=checkUserI3D(rfidTagNum, digAccessCodeUL) ;  
  userMask2=checkUserI3D(rfidTagNum,keypadValue) ; //this is the pressed in value
  Serial.println("userMask2") ;
  Serial.println(userMask2,DEC) ;


  if(userMask2>=0){    
    switch(userMask2) {
 
   case 0:                         // No outside privs, do not log denied.
    {                              // authenticate and log only.
    logAccessGranted(rfidTagNum, 2);
    break;
    }
      
  case 10:                         // Authenticating immediately locks up and arms alarm
    {                              // 
    logAccessGranted(rfidTagNum, 2);
    runCommand(0x2);
    break;
    }
    
   case 20:                                               //Limited hours user
    {
    ds1307.getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);    
    if((hour >=17) && (hour <=23)){
         logAccessGranted(rfidTagNum, 2);                    // Log and unlock door 2
         alarmState(0);
         armAlarm(0);                                     //  Deactivate Alarm                           
         door2locktimer=millis();
         doorUnlock(2);                                   // Unlock the door.
         keypadGranted=1;
    }
     break;
    }
  
   //ML ADDED  this case allows us to introduce a user mask, which is used in the format of adding a user
   //    The user mask is 32
   //    Details of case 32 are now the same details as the default.
   //    This can be changed upon dicision
   //   
   case 32:
   {
         logAccessGranted(rfidTagNum, 2);           // Log and unlock door 2 -- assuming keypad use
         alarmState(0);
         armAlarm(0);                            //  Deactivate Alarm                          
         door2locktimer=millis();
         doorUnlock(2);                          // Unlock the door.
         keypadGranted=1;
         break;
   }
   
   
   case 255:                                               // Locked out      
    {
     Serial.print("User ");
     Serial.print(userMask2,DEC);
     Serial.println(" locked out.");
     break;
    }
    
    default:  
    {            
         logAccessGranted(rfidTagNum, 2);           // Log and unlock door 2
         alarmState(0);
         armAlarm(0);                            //  Deactivate Alarm                          
         door2locktimer=millis();
         doorUnlock(2);                          // Unlock the door.
         keypadGranted=1;
         break;
    }
                 }                                      
     //RESETTING for next tag scan
    //reset the rfidNumOk and the keyPadNumOk
    rfidNumOk = 0 ;  // reset rfid tag not yet read
    rfidTagNum = 0  ;  //reset this here 
    keyPadNumOk = 0 ;  //reset keyPadNumOk
    wiegand26.initReaderTwo();                   //  Reset for next tag scan  
  }
    else 
    {                    
     //if(checkSuperuser(reader2) >= 0) {         
     if(checkSuperuser(rfidTagNum) > 0) {              // Check if a superuser, grant access. note change to greater than 0
      logAccessGranted(rfidTagNum, 2);                 // Log and unlock door 2
         alarmState(0);
         armAlarm(0);                              //  Deactivate Alarm
         chirpAlarm(1);                            
         door1locktimer=millis();
         doorUnlock(1);                            // Unlock the door.
         keypadGranted=1;
                                      }
      else{                                
      logAccessDenied(rfidTagNum,2);                 //  no tickee, no laundree
          }
    }
    
   
         
       /**       We do NOT use any keypad commands                          
   if(keypadGranted==1) 
    {
      while((millis() - keypadTime)  <=KEYPADTIMEOUT){

                                                              // If access granted, open 5 second window for pin pad commands.
        if(reader2Count >=4){
          if(reader2 !=0xB){                                  // Pin pad command can be any length, terminated with '#' on the keypad.
            if(keypadValue ==0){                              // This 0..9, A..F encoding works with many Wiegand-format keypad or reader 
              keypadValue = reader2;                          // plus keypad units.

            }
            else if(keypadValue !=0) {
              keypadValue = keypadValue <<4;
              keypadValue |= reader2;               
            }
            wiegand26.initReaderTwo();                         //Reset reader one and move on.
          } 
          else break;

        }

      }

        logkeypadCommand(2,keypadValue);
        runCommand(keypadValue);                              // Run any commands entered at the keypads.
        wiegand26.initReaderTwo();
      

   }  
   */
    wiegand26.initReaderTwo();                    
  } 


  /* Check physical sensors with 
   the logic below. Behavior is based on
   the current alarmArmed value.
   0=disarmed 
   1=armed
   2=
   3=
   4=door chime only (Unlock DOOR1, Check zone 0/chirp alarm if active)
   
   Modify the alarm sequence to meet your needs.
   */

  switch(alarmArmed) {


 case 0:
  {
    break;                                        // Alarm is not armed, do nothing.  
  }

    case 1:                                       // Alarm is armed
  {                                                                                         
      if(alarmActivated==0){                       // If alarm is armed but not currently alarming, check sensor zones.

          if(pollAlarm(0) == 1 ){                   // If this zone is tripped, immediately set Alarm State to 2 (alarm delay).
              alarmState(2);                        // Also starts the delay timer    
              alarmDelay=millis();
              if(sensor[0]==false) {                // Only log and save if sensor activation is new.
               logalarmSensor(0);
               EEPROM.write(EEPROM_ALARM,0);        // Save the alarm sensor tripped to eeprom                                      
               sensor[0]=true;                      // Set value to not log this again                                                                        
              }
           } 
          if(pollAlarm(1) == 1 ){                  // If this zone is tripped, immediately set Alarm State to 1 (alarm immediate).
            alarmState(1);      
             if(sensor[1]==false) {                // Only log and save if sensor activation is new.
              logalarmSensor(1);
              EEPROM.write(EEPROM_ALARM,1);        // Save the alarm sensor tripped to eeprom                                     
              sensor[1]=true;                      // Set value to not log this again
             }  
          }
          if(pollAlarm(2) == 1 ){                  // If this zone is tripped, immediately set Alarm State to 1 (alarm immediate).
            alarmState(1);      
             if(sensor[2]==false) {                // Only log and save if sensor activation is new.
              logalarmSensor(2);
              EEPROM.write(EEPROM_ALARM,2);        // Save the alarm sensor tripped to eeprom                                     
              sensor[2]=true;                      // Set value to not log this again
             }    

           } 
           
          if(pollAlarm(3) == 1 ){                   // If this zone is tripped, immediately set Alarm State to 2 (alarm delay).
              alarmState(2);                        // Also starts the delay timer    
              alarmDelay=millis();
              if(sensor[3]==false) {                // Only log and save if sensor activation is new.
               logalarmSensor(3);
               EEPROM.write(EEPROM_ALARM,3);        // Save the alarm sensor tripped to eeprom                                      
               sensor[3]=true;                      // Set value to not log this again                                                                        
              }
           }                                                                                                                               
      }
   if(alarmActivated==1)  {                         // If alarm is actively going off (siren/strobe) for 10 min (6e5=10min)
    if(millis()-alarmSirenTimer >=3.6e6)            // Check for alarm interval expired and turn off if needed
     {
      digitalWrite(ALARMSIRENPIN,LOW);              // Turn on the chime instead  
      digitalWrite(ALARMSTROBEPIN,HIGH);     
     }
                           }  
   if(alarmActivated==2)  {                         // If alarm is activated on delay, take this action
    if(millis()-alarmDelay >=60000)                 // Turn on the siren once delay exceeds 60sec.
     {
      alarmState(1);                          
     }
                           }   
      break;
  }
  
  case 4: 
    {                                                // Door chime mode   
      if((pollAlarm(3) !=0) && (doorChime==false)) {   // Only activate door chime once per opening
        chirpAlarm(3);                  
        logChime();
        doorChime=true;   
         }
      if(pollAlarm(3) ==0){
        doorChime=false;   }
        break;    
    }
  default: 
    {
      break;  
    }
  }
  
// Log all motion detector activations regardless of alarm armed state. Useful for "occupancy detection"

          if(pollAlarm(0) == 1 ){                  // If this zone is tripped, log the action only
          //  if(sensor[0]==false) 
          if((millis() - sensorDelay[0]) >=7500) {
           logalarmSensor(0);   
           sensorDelay[0]=millis();                                                                  
           sensor[0]=true;      }                 // Set value to not log this again for 7.5s              
           }

          if(pollAlarm(1) == 1 ){                  // If this zone is tripped, log the action only
         //   if(sensor[1]==false) 
          if((millis() - sensorDelay[1]) >=7500) {
           logalarmSensor(1);   
           sensorDelay[1]=millis();                                                            
           sensor[1]=true;                       // Set value to not log this again for 7.5s
          }           
         }
         
  } // End of loop()


void runCommand(long command) {         // Run any commands entered at the pin pad.

  switch(command) {                              


  case 0x1: 
    {                                     // If command = 1, deactivate alarm
      alarmState(0);                      // Set global alarm level variable
      armAlarm(0);
      chirpAlarm(1);
      break;  
    }

  case 0x2: 
    {                                       // If command =2, activate alarm with delay.

      doorUnlock(1);                        // Set global alarm level variable
      door1Locked=false;
      doorClosed=false;                      // 200 chirps = ~30 seconds delay

   if((pollAlarm(3) == 0) && (pollAlarm(2) == 0)) {                  // Do not arm the alarm if doors are open

     for(byte i=0; i<30; i++) {
         if((pollAlarm(3) !=0) && doorClosed==false) {             // Set door to be unlocked until alarm timeout or user exits
          lockall();    
          doorClosed=true; 
         }      
         digitalWrite(ALARMSTROBEPIN, HIGH);
         delay(500);
         digitalWrite(ALARMSTROBEPIN, LOW);
         delay(500);                        
      }
      chirpAlarm(2);
      armAlarm(1);                 
      lockall();                                                  // Lock all doors on exit
   }
  else {                                                          // Beep the alarm once and exit if attempt made to arm alarm with doors open
         digitalWrite(ALARMSTROBEPIN, HIGH);
         delay(500);
         digitalWrite(ALARMSTROBEPIN, LOW);
         delay(500);                        
         lockall();                                                  // Lock all doors anyway
       }
      break; 
    }
    
  case 0x3: 
    {

      doorLock(1);                       // Set door 2 to stay unlocked, and door 1 to be locked
      doorUnlock(2);
      door1Locked=true;
      door2Locked=false;
      chirpAlarm(3);   
      break;
    }

  case 0x4:                               // Set doors to remain open
    {
      armAlarm(4);
      doorUnlock(1);
      doorUnlock(2);
      door1Locked=false;
      door2Locked=false;
      chirpAlarm(4);   
      break;
    }
  case 0x5:                               // Relock all doors
    {
      lockall();
      chirpAlarm(5);   
      break;  
    }

  case 0x911: 
    {
      chirpAlarm(9);          // Emergency
      armAlarm(1);                   
      alarmState(1);
      break;  
    }

  case 0x20: 
    {                                   // If command = 20, do nothing
      break;
    }    
  default: 
    {       
      break;      
    }  
  }


}  


/* Alarm System Functions - Modify these as needed for your application. 
 Sensor zones may be polled with digital or analog pins. Unique reader2
 resistors can be used to check more zones from the analog pins.
 */

void alarmState(byte alarmLevel) {                    //Changes the alarm status based on this flow

  logalarmState(alarmLevel); 
  switch (alarmLevel) {                              
  case 0: 
    {                                                 // If alarmLevel == 0 turn off alarm.   
      digitalWrite(ALARMSIRENPIN, LOW);
      digitalWrite(ALARMSTROBEPIN, LOW);
      alarmActivated = alarmLevel;                    //Set global alarm level variable
      break;  
    }        
  case 1: 
    { 
      digitalWrite(ALARMSIRENPIN, HIGH);               // If alarmLevel == 1 turn on strobe lights and siren
  //    digitalWrite(ALARMSTROBEPIN, HIGH);            // Optionally activate yoru strobe/chome
      alarmSirenTimer=millis();
      alarmActivated = alarmLevel;                    //Set global alarm level variable
      logalarmTriggered();

      break;  
    }        

  case 2:                                        
    {
      digitalWrite(ALARMSTROBEPIN, HIGH);   
      alarmActivated = alarmLevel;
      break;    
    }

  case 3:                                        
    {

      alarmActivated = alarmLevel;
      break;    
    }
    /*
      case 4: {
     vaporize_intruders(STUN);
     break;
     }
     
     case 5: {
     vaporize_intruders(MAIM);
     }  etc. etc. etc.
     break;
     */

  default: 
    {                                            // Exceptional cases kill alarm outputs
      digitalWrite(ALARMSIRENPIN, LOW);          // Turn off siren and strobe
     // digitalWrite(ALARMSTROBEPIN, LOW);        
      break;
    } 


 

  }

      if(alarmActivated != EEPROM.read(EEPROM_ALARM)){    // Update eeprom value
         EEPROM.write(EEPROM_ALARM,alarmActivated); 
         }

}  //End of alarmState()

void chirpAlarm(byte chirps){            // Chirp the siren pin or strobe to indicate events.      
  for(byte i=0; i<chirps; i++) {
    digitalWrite(ALARMSTROBEPIN, HIGH);
    delay(100);
    digitalWrite(ALARMSTROBEPIN, LOW);
    delay(200);                              
  }    
}                                   

byte pollAlarm(byte input){

  // Return 1 if sensor shows < pre-defined voltage.
  delay(20);
  if(abs((analogRead(analogsensorPins[input])/4) - EEPROM.read(EEPROM_ALARMZONES+input)) >SENSORTHRESHOLD){
    return 1;

  }
  else return 0;
}

void trainAlarm(){                       // Train the system about the default states of the alarm pins.
  armAlarm(0);                           // Disarm alarm first
  alarmState(0);

  int temp[5]={0};
  int avg;

  for(int i=0; i<numAlarmPins; i++) {         

    for(int j=0; j<5;j++){                          
      temp[j]=analogRead(analogsensorPins[i]);
      delay(50);                                         // Give the readings time to settle
    }
    avg=((temp[0]+temp[1]+temp[2]+temp[3]+temp[4])/20);  // Average the results to get best values
    Serial.print("Sensor ");
    Serial.print(i);
    Serial.print(" ");
    Serial.print("value:");
    Serial.println(avg);
    EEPROM.write((EEPROM_ALARMZONES+i),byte(avg));   //Save results to EEPROM
    avg=0;
  }

  logDate();
  PROGMEMprintln(alarmtrainMessage);


}

void armAlarm(byte level){                       // Arm the alarm and set to level
  alarmArmed = level;
  logalarmArmed(level);

  sensor[0] = false;                             // Reset the sensor tripped values
  sensor[1] = false;
  sensor[2] = false;
  sensor[3] = false;

  if(level != EEPROM.read(EEPROM_ALARMARMED)){ 
    EEPROM.write(EEPROM_ALARMARMED,level); 
  }
}


/* Access System Functions - Modify these as needed for your application. 
 These function control lock/unlock and user lookup.
 */

int checkSuperuser(long input){       // Check to see if user is in the user list. If yes, return their index value.
int found=-1;
  for(int i=0; i<=numUsers; i++){   
    if(input == superUserList[i]){
      logDate();
      Serial.print("Superuser ");
      Serial.print(i,DEC);
      Serial.println(" found.");
      found=i;
      return found;    
    }
  }                   
 
  return found;             //If no, return -1
}


void doorUnlock(int input) {          //Send an unlock signal to the door and flash the Door LED
byte dp=1;
  if(input == 1) {
    dp=DOORPIN1; }
   else(dp=DOORPIN2);
  
  digitalWrite(dp, HIGH);
  Serial.print("Door ");
  Serial.print(input,DEC);
  Serial.println(" unlocked");

}

void doorLock(int input) {          //Send an unlock signal to the door and flash the Door LED
byte dp=1;
  if(input == 1) {
    dp=DOORPIN1; }
   else(dp=DOORPIN2);

  digitalWrite(dp, LOW);
  Serial.print("Door ");
  Serial.print(input,DEC);
  Serial.println(" locked");

}
void lockall() {                      //Lock down all doors. Can also be run periodically to safeguard system.

  digitalWrite(DOORPIN1, LOW);
  digitalWrite(DOORPIN2,LOW);
  door1Locked=true;
  door2Locked=true;
  PROGMEMprintln(doorslockedMessage);

}

/* Logging Functions - Modify these as needed for your application. 
 Logging may be serial to USB or via Ethernet (to be added later)
 */


void PROGMEMprintln(const prog_uchar str[])    // Function to retrieve logging strings from program memory
{                                              // Prints newline after each string  
  char c;
  if(!str) return;
  while((c = pgm_read_byte(str++))){
    Serial.print(c,BYTE);
                                   }
    Serial.println();
}

void PROGMEMprint(const prog_uchar str[])    // Function to retrieve logging strings from program memory
{                                            // Does not print newlines
  char c;
  if(!str) return;
  while((c = pgm_read_byte(str++))){
    Serial.print(c,BYTE);
                                   }

}


void logDate()
{
  ds1307.getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
  Serial.print(hour, DEC);
  Serial.print(":");
  Serial.print(minute, DEC);
  Serial.print(":");
  Serial.print(second, DEC);
  Serial.print("  ");
  Serial.print(month, DEC);
  Serial.print("/");
  Serial.print(dayOfMonth, DEC);
  Serial.print("/");
  Serial.print(year, DEC);
  Serial.print(' ');
  
  switch(dayOfWeek){

    case 1:{
     Serial.print("SUN");
     break;
           }
    case 2:{
     Serial.print("MON");
     break;
           }
    case 3:{
     Serial.print("TUE");
     break;
          }
    case 4:{
     Serial.print("WED");
     break;
           }
    case 5:{
     Serial.print("THU");
     break;
           }
    case 6:{
     Serial.print("FRI");
     break;
           }
    case 7:{
     Serial.print("SAT");
     break;
           }  
  }
  
  Serial.print(" ");

}

void logReboot() {                                  //Log system startup
  logDate();
    PROGMEMprintln(rebootMessage);
}

void logChime() {
  logDate();
    PROGMEMprintln(doorChimeMessage);
}

void logTagPresent (long user, byte reader) {     //Log Tag Presented events
  logDate();
  Serial.print("User ");
 if(DEBUG==2){ Serial.print(user,HEX);}
  Serial.print(" presented tag at reader ");
  Serial.println(reader,DEC);
}

void logAccessGranted(long user, byte reader) {     //Log Access events
  logDate();
  Serial.print("User ");
 if(DEBUG==2){Serial.print(user,HEX);}
  Serial.print(" granted access at reader ");
  Serial.println(reader,DEC);
}                                         

void logAccessDenied(long user, byte reader) {     //Log Access denied events
  logDate();
  Serial.print("User ");
 if(DEBUG==1){Serial.print(user,HEX);} 
  Serial.print(" denied access at reader ");
  Serial.println(reader,DEC);
}   

void logkeypadCommand(byte reader, long command){
  logDate();
  Serial.print("Command ");
  Serial.print(command,HEX);
  Serial.print(" entered at reader ");
  Serial.println(reader,DEC);
}  




void logalarmSensor(byte zone) {     //Log Alarm zone events
  logDate();
  Serial.print("Zone ");
  Serial.print(zone,DEC);
  Serial.println(" sensor activated");
}

void logalarmTriggered() {
      logDate();
      Serial.println("Alarm triggered!");   // This phrase can be scanned for by alerting scripts.
 }

void logunLock(long user, byte door) {        //Log unlock events
  logDate();
  Serial.print("User ");
  Serial.print(user,DEC);
  Serial.print(" unlocked door ");
  Serial.println(door,DEC);

}

void logalarmState(byte level) {        //Log unlock events
  logDate();
  Serial.print("Alarm level changed to ");
  Serial.println(level,DEC);
}

void logalarmArmed(byte level) {        //Log unlock events
  logDate();
  Serial.print("Alarm armed level changed to ");
  Serial.println(level,DEC);
}

void logprivFail() {
//  Serial.println("Priv mode disabled");
PROGMEMprintln(privsdeniedMessage);
                   }


void hardwareTest(long iterations)
{

  /* Hardware testing routing. Performs a read of all digital inputs and
   * a write to each relay output. Also reads the analog value of each
   * alarm pin. Use for testing hardware. Wiegand26 readers should read 
   * "HIGH" or "1" when connected.
   */

  pinMode(2,INPUT);
  pinMode(3,INPUT);
  pinMode(4,INPUT);
  pinMode(5,INPUT);

  pinMode(6,OUTPUT);
  pinMode(7,OUTPUT);
  pinMode(8,OUTPUT);
  pinMode(9,OUTPUT);

  for(long counter=1; counter<=iterations; counter++) {                                  // Do this number of times specified
    logDate();
    Serial.print("\n"); 
    Serial.println("Pass: "); 
    Serial.println(counter); 
    Serial.print("Input 2:");                    // Digital input testing
    Serial.println(digitalRead(2));
    Serial.print("Input 3:");
    Serial.println(digitalRead(3));
    Serial.print("Input 4:");
    Serial.println(digitalRead(4));
    Serial.print("Input 5:");
    Serial.println(digitalRead(5));
    Serial.print("Input A0:");                   // Analog input testing
    Serial.println(analogRead(0));
    Serial.print("Input A1:");
    Serial.println(analogRead(1));
    Serial.print("Input A2:");
    Serial.println(analogRead(2));
    Serial.print("Input A3:");
    Serial.println(analogRead(3));
    delay(5000);

    digitalWrite(6,HIGH);                         // Relay exercise routine
    digitalWrite(7,HIGH);
    digitalWrite(8,HIGH);
    digitalWrite(9,HIGH);
    Serial.println("Relays 0..3 on");
    delay(2000);
    digitalWrite(6,LOW);
    digitalWrite(7,LOW);
    digitalWrite(8,LOW);
    digitalWrite(9,LOW);
    Serial.println("Relays 0..3 off");

  }
}

// good for both original code and for I3D usage
void clearUsers()    //Erases all users from EEPROM
{
  for(int i=EEPROM_FIRSTUSER; i<=EEPROM_LASTUSER; i++){
    EEPROM.write(i,0);  
    logDate();
    Serial.println("User database erased.");  
  }
}

void addUser(int userNum, byte userMask, unsigned long tagNumber)       // Modifies a user an entry in the local database.
{                                                                       // Users number 0..NUMUSERS
  int offset = (EEPROM_FIRSTUSER+(userNum*5));                          // Find the offset to write this user to
  byte EEPROM_buffer[5] ={0};                                           // Buffer for creating the 4 byte values to write. Usermask is stored in byte 5.

  logDate();

  if((userNum <0) || (userNum > NUMUSERS)) {                            // Do not write to invalid EEPROM addresses.

    Serial.print("Invalid user modify attempted.");
  }
  else
  {
    EEPROM_buffer[0] = byte(tagNumber &  0xFFF);   // Fill the buffer with the values to write to bytes 0..4 
    EEPROM_buffer[1] = byte(tagNumber >> 8);
    EEPROM_buffer[2] = byte(tagNumber >> 16);
    EEPROM_buffer[3] = byte(tagNumber >> 24);
    EEPROM_buffer[4] = byte(userMask);

    for(int i=0; i<5; i++){
      EEPROM.write((offset+i), (EEPROM_buffer[i])); // Store the resulting value in 5 bytes of EEPROM.

    }
    Serial.print("User ");
    Serial.print(userNum,DEC);
    Serial.println(" successfully modified"); 
  }
}  //end addUser



//this invoked with the p command
void addUserI3D(int userNum, byte userMask, unsigned long tagNumber, unsigned long accessCode)       // Modifies a user an entry in the local database.
{                                                                       // Users number 0..NUMUSERS
  int offset = (EEPROM_FIRSTUSER+(userNum*9));                          // Find the offset to write this user to
  byte EEPROM_buffer[9] ={0};                                           // Buffer for creating the 4 byte values to write. Usermask is stored in byte 5.

  logDate();

  if((userNum <0) || (userNum > NUMUSERS)) {                            // Do not write to invalid EEPROM addresses.

    Serial.print("Invalid user modify attempted.");
  }
  else
  {
    EEPROM_buffer[0] = byte(tagNumber &  0xFFF);   // Fill the buffer with the values to write to bytes 0..4 
    EEPROM_buffer[1] = byte(tagNumber >> 8);
    EEPROM_buffer[2] = byte(tagNumber >> 16);
    EEPROM_buffer[3] = byte(tagNumber >> 24);
    EEPROM_buffer[4] = byte(userMask);
    //fill the buffer with values to write to bytes 5...8
    EEPROM_buffer[5] = byte(accessCode & 0XFFF);
    EEPROM_buffer[6] = byte(accessCode >> 8);
    EEPROM_buffer[7] = byte(accessCode >> 16);
    EEPROM_buffer[8] = byte(accessCode >> 24);

    for(int i=0; i<9; i++){
      EEPROM.write((offset+i), (EEPROM_buffer[i])); // Store the resulting value in 9 bytes of EEPROM.
    }
    Serial.print("User ");
    Serial.print(userNum,DEC);
    Serial.println(" successfully modified"); 
  }
}  //end addUserI3D


void deleteUser(int userNum)                                            // Deletes a user from the local database.
{                                                                       // Users number 0..NUMUSERS
  int offset = (EEPROM_FIRSTUSER+(userNum*5));                          // Find the offset to write this user to
  
  logDate();

  if((userNum <0) || (userNum > NUMUSERS)) {                            // Do not write to invalid EEPROM addresses.

    Serial.print("Invalid user delete attempted.");
  }
  else
  {
    for(int i=0; i<5; i++){
     
      EEPROM.write((offset+i), 0xFF); // Store the resulting value in 5 bytes of EEPROM.
                                                    // Starting at offset
    }
    Serial.print("User deleted at position "); 
    Serial.println(userNum);
  }
} //end deleteUser 





void deleteUserI3D(int userNum)                                            // Deletes a user from the local database.
{                                                                       // Users number 0..NUMUSERS
  int offset = (EEPROM_FIRSTUSER+(userNum*9));                          // Find the offset to write this user to
  
  logDate();

  if((userNum <0) || (userNum > NUMUSERS)) {                            // Do not write to invalid EEPROM addresses.
    Serial.print("Invalid user delete attempted.");
  }
  else
  {
    for(int i=0; i<9; i++){
      EEPROM.write((offset+i), 0xFF); // Store the resulting value in 9 bytes of EEPROM.
                                                    // Starting at offset
    }
    Serial.print("User deleted at position "); 
    Serial.println(userNum);
  }
}//end deleteUserI3D



int checkUser(unsigned long tagNumber)                                  // Check if a particular tag exists in the local database. Returns userMask if found.
{                                                                       // Users number 0..NUMUSERS
  // Find the first offset to check

  unsigned long EEPROM_buffer=0;                                         // Buffer for recreating tagNumber from the 4 stored bytes.
  int found=-1;
  
  logDate();
 // ML added the print to serial
  //Serial.println("in checkUser") ;
  //Serial.println("tagNumber = ");
  Serial.println(tagNumber) ;
  
  for(int i=EEPROM_FIRSTUSER; i<=(EEPROM_LASTUSER-5); i=i+5){
    EEPROM_buffer=0;
    
    EEPROM_buffer=(EEPROM.read(i+3));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+2));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+1));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i));

    if((EEPROM_buffer == tagNumber) && (tagNumber !=0xFFFFFFFF) && (tagNumber !=0x0)) {    // Return a not found on blank (0xFFFFFFFF) entries 
      logDate();
      Serial.print("User ");
      Serial.print(((i-EEPROM_FIRSTUSER)/5),DEC);
      Serial.println(" authenticated.");
      found = EEPROM.read(i+4);
      return found;
    }                             
  }
  Serial.println("User not found");
  delay(1000);                                                            // Delay to prevent brute-force attacks on reader
  return found;                        
}


int checkUserI3D(unsigned long tagNumber, unsigned long userAccessCodeUL)                                  // Check if a particular tag exists in the local database. Returns userMask if found.
{                                                                       // Users number 0..NUMUSERS
  // Find the first offset to check
  unsigned long EEPROM_buffer=0;        // Buffer for recreating tagNumber from the 4 stored bytes.
  int found=-1;
  
  unsigned long EEPROM_buffer2 = 0 ;  //Buffer for recreating userAccessCodeUL from the last 4 stored bytes
  
  logDate();

 // ML added the print to serial
  Serial.println("in checkUserI3D") ;
  Serial.print("rifdtagNumber = ");
  Serial.println(tagNumber) ;
  Serial.println("userAccessCodeUL = ") ;
  Serial.println(userAccessCodeUL) ;
  
  
  for(int i=EEPROM_FIRSTUSER; i<=(EEPROM_LASTUSER-9); i=i+9){
    EEPROM_buffer=0;
    EEPROM_buffer2 = 0 ;
    
    EEPROM_buffer=(EEPROM.read(i+3));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+2));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+1));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i));

    EEPROM_buffer2=(EEPROM.read(i+8));
    EEPROM_buffer2= EEPROM_buffer2<<8;
    EEPROM_buffer2=(EEPROM_buffer2 ^ EEPROM.read(i+7));
    EEPROM_buffer2= EEPROM_buffer2<<8;
    EEPROM_buffer2=(EEPROM_buffer2 ^ EEPROM.read(i+6));
    EEPROM_buffer2= EEPROM_buffer2<<8;
    EEPROM_buffer2=(EEPROM_buffer2 ^ EEPROM.read(i+5));

  //the user is checked by the access code and by the rfid tag number -- we only will print out the tag number
   if (EEPROM_buffer2 == userAccessCodeUL)
   {
    if((EEPROM_buffer == tagNumber) && (tagNumber !=0xFFFFFFFF) && (tagNumber !=0x0)) {    // Return a not found on blank (0xFFFFFFFF) entries 
      logDate();
      Serial.print("User ");
      Serial.print(((i-EEPROM_FIRSTUSER)/9),DEC);
      Serial.println(" authenticated.");
      found = EEPROM.read(i+4);
      return found;
      }                             
    }
  }
  Serial.println("User not found");
  delay(1000);                                                            // Delay to prevent brute-force attacks on reader
  return found;                        
} //end checkUserI3D  



void dumpUser(byte usernum)                                            // Return information ona particular entry in the local DB
{                                                                      // Users number 0..NUMUSERS
  unsigned long EEPROM_buffer=0;                                       // Buffer for recreating tagNumber from the 4 stored bytes.

  if((0<=usernum) && (usernum <=199)){
    int i=usernum*5+EEPROM_FIRSTUSER;

    EEPROM_buffer=0;
    EEPROM_buffer=(EEPROM.read(i+3));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+2));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+1));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i));

    Serial.print(((i-EEPROM_FIRSTUSER)/5),DEC);
    Serial.print("\t");
    Serial.print(EEPROM.read(i+4),DEC);
    Serial.print("\t");


    if(DEBUG==2){
      Serial.println(EEPROM_buffer,HEX);
                 }
     else {
           if(EEPROM_buffer != 0xFFFFFFFF) {
             Serial.print("********");}
           }
  }
  else Serial.println("Bad user number!");
} //end dumpUser


void dumpUserI3D(byte usernum)                                            // Return information ona particular entry in the local DB
{                                                                      // Users number 0..NUMUSERS
  unsigned long EEPROM_buffer=0;                                       // Buffer for recreating rfid tagNumber from the 4 stored bytes for that variable.
  unsigned long EEPROM_buffer2 = 0 ;   //buffer for recreating access code number from 4 stored bytes

  //this should be globals
  //  for 9 bytes per user and having 1024 total bytes, usernum <= 111
  if((0<=usernum) && (usernum <=111)){

    int i=usernum*9 + EEPROM_FIRSTUSER;

 //only checking on the rfid tag
    EEPROM_buffer=0;
    EEPROM_buffer=(EEPROM.read(i+3));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+2));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i+1));
    EEPROM_buffer= EEPROM_buffer<<8;
    EEPROM_buffer=(EEPROM_buffer ^ EEPROM.read(i));

    Serial.print(((i-EEPROM_FIRSTUSER)/9),DEC);
    Serial.print("\t");
    Serial.print(EEPROM.read(i+4),DEC);  //this is the user mask associated with the rfid tag
    Serial.print("\t");

    // this is the buffer for recreating the user access code
    EEPROM_buffer2 = 0 ;
    EEPROM_buffer2=(EEPROM.read(i+8));
    EEPROM_buffer2= EEPROM_buffer2<<8;
    EEPROM_buffer2=(EEPROM_buffer2 ^ EEPROM.read(i+7));
    EEPROM_buffer2= EEPROM_buffer2<<8;
    EEPROM_buffer2=(EEPROM_buffer2 ^ EEPROM.read(i+6));
    EEPROM_buffer2= EEPROM_buffer2<<8;
    EEPROM_buffer2=(EEPROM_buffer2 ^ EEPROM.read(i+5));


    if(DEBUG==2){
      //Serial.println("DEBUG statements in DumpUserI3D") ;
      //Serial.println(EEPROM_buffer) ;
      Serial.println(EEPROM_buffer,HEX);
      Serial.println(EEPROM_buffer2,HEX) ;
                 }
     else {
           if(EEPROM_buffer != 0xFFFFFFFF) {
             Serial.print("********");}
           }
  }
  else Serial.println("Bad rfid tag, so bad user number!");
} //end dumpUserI3D

/* Displays a serial terminal menu system for
 * user management and other tasks
 */

void readCommand() {                                               
                                                                    
//ML  this is not correct -- it is giving a (
//byte stringSize=(sizeof(inString)/sizeof(char));
//ML  I believe what is needed is
//byte stringSize = sizeof(inString) ;

//initialize
char cmdString[5][10] = {0} ;  //cmdCode number  mask  rfidTag  accessCode form the separate strings


byte j=0;                                                          // Counters
byte k=0;
char cmd=0;

byte index = 0 ;
char ch;
//ML Added the > 0 

//set the inString to a value of q
//  not allowed to use q in the commands
for (byte i=0; i < 50; i++)  // change to 50
{
  inString[i] = NULL ;
}

 while (Serial.available() > 0 ) 
 {    
 // Check if user entered a command this round	 
   if (  index <   50   )
   {
     ch = Serial.read();   
     inString[index] =  ch ;    
     index++ ;
   }
  //Serial.print(ch);                        // Turns echo on or off
  // Serial.println(index) ;
  // Serial.println(ch) ;
 }

  if (index!= 0) 
  { //added by ML because equal zero does not go through
  //if(inCount==0) 
  //Serial.println("inside index if and index val is ") ;
  //Serial.println(index) ;
  
  //now break up the string
  for(byte i=0;  i< index  ; i++) 
  {
    cmdString[j][k] = inString[i];
    if(k<9) 
       k++;
    else break;
 
    if(inString[i] == ' ') // Check for space and if true, terminate string and move to next string.
    {
      cmdString[j][k-1]=0;
      if(j<4)  
          j++;
      else break;
          k=0;             
    }
   }
  //Serial.println("j val is");
  //Serial.println(j) ;
  // for (byte mm = 0; mm < j+1 ; mm++)
  //{
  //  Serial.println(cmdString[mm]) ; 
  //}
  cmd = cmdString[0][0];
   //Serial.println("cmd is ") ;
   //Serial.println(cmd) ;
                                       
               switch(cmd) {


                 case 'e': {                                                 // Enable "privileged" commands at console
                   logDate();

                     if((consoleFail>=5) && (millis()-consolefailTimer<300000))  // Do not allow priv mode if more than 5 failed logins in 5 minute
                       {  
                         PROGMEMprintln(privsAttemptsMessage);
                         break;
                       }
                        if (strtoul(cmdString[1],NULL,16) == PRIVPASSWORD)
                         {
                         consoleFail=0;                    
                         PROGMEMprintln(privsenabledMessage);
                         privmodeEnabled=true;
                         }
                        else {
                          PROGMEMprintln(privsdisabledMessage);
                          privmodeEnabled=false;                                          
                           if(consoleFail==0) {                                   // Set the timeout for failed logins
                             consolefailTimer=millis();
                                              }
                              consoleFail++;                                    // Increment the login failure counter
                                            }
                      
                   break;
                
                            }
                
                 //privmodeEnabled=true;            //Debugging statement

                
                 case 'a': {                                                 
                   // List whole user database
                  if(privmodeEnabled==true) {    
                      Serial.println(" in case statement a");             
                      //logDate();
                      Serial.println("User dump started.");
                      Serial.print("UserNum:");
                      Serial.print(" ");
                      Serial.print("Usermask:");
                      Serial.print(" ");
                      Serial.println("RFIDTagNum:");
                      Serial.print(" ");
              
                      for(int i=0; i<(NUMUSERS); i++)
                      {
                        dumpUser(i);
                        Serial.println();
                       }
                   }
                 else{logprivFail();}
                  break;
                           }
                           
                           
                  case 'A': 
                  {                                               
                   // List whole user database of i3d users with the extra info on access code
                  if(privmodeEnabled==true) {    
                      Serial.println(" in case statement A");             
                      //logDate();
                     // Serial.println("User dump i3d started.");
                      
                      for(int i=0; i<(NUMUSERS); i++)
                      {
                        dumpUserI3D(i);
                        Serial.println();
                       }
                   }
                 else{logprivFail();}
                  break;
                           }         
 
                 case 's': {                                                 // List user 
                  if(privmodeEnabled==true) {
                     Serial.print("UserNum:");
                     Serial.print(" ");
                     Serial.print("Usermask:");
                     Serial.print(" ");
                     Serial.println("TagNum:");
                     dumpUser(atoi(cmdString[1]));
                     Serial.println();
                                             }
                 else{logprivFail();}
                  break;
                           }
 
                 case 'S': {         // List userI3D
                  if(privmodeEnabled==true) {
                     Serial.print("in S") ;
                     Serial.print(atoi(cmdString[1])) ;
                     //Serial.print("UserNum:");
                    // Serial.print(" ");
                    // Serial.print("Usermask:");
                    // Serial.print(" ");
                    // Serial.println("TagNum:");
                     dumpUserI3D(atoi(cmdString[1]));  //mdString[1] is the usernum
                     Serial.println();
                                             }
                 else{logprivFail();}
                  break;
                           }
 
                  case 'd': {                                                 // Display current time
                   Serial.println(" in the d case statement cmd"); 
                   logDate();
                   Serial.println();
                   break;
                            }

                  case '1': {                                               // Deactivate alarm                                       
                 if(privmodeEnabled==true) 
                 {
                   armAlarm(0);
                   alarmState(0);
                   chirpAlarm(1);  
                   }
                   else{
                     logprivFail();
                   }
                   break;
                            }
                            
                  case '2': {                                               // Activate alarm with delay.
                      chirpAlarm(20);                                          // 200 chirps = ~30 seconds delay
                      armAlarm(1);                           
                      break; 
                            } 

                  case 'u': {
                    if(privmodeEnabled==true) {
                      alarmState(0);                                       // Set to door chime only/open doors                                                                       
                      armAlarm(4);
                      doorUnlock(1);
                      doorUnlock(2);
                      door1Locked=false;
                      door2Locked=false;
                      chirpAlarm(3)   ;
                     }
                                               
                   else
                   {
                     logprivFail();
                   }
                   break;  
                  }
                            
                  case 'l': {                                             // Lock all doors          
                   lockall();
                   chirpAlarm(1);   
                   break;  
                            }                            

                   case '3': {                                            // Train alarm sensors
                  if(privmodeEnabled==true) {
                   trainAlarm();
                                            }
                   else{logprivFail();}
                   break;
                             }
                             
                             
                   case '9': {                                            // Show site status
                    PROGMEMprint(statusMessage1);
                    Serial.println(alarmArmed,DEC);
                    PROGMEMprint(statusMessage2);
                    Serial.println(alarmActivated,DEC);
                    PROGMEMprint(statusMessage3);
                    Serial.println(pollAlarm(3),DEC);
                    PROGMEMprint(statusMessage4);
                    Serial.println(pollAlarm(2),DEC);                  
                    PROGMEMprint(statusMessage5); 
                    Serial.println(door1Locked);                    
                    PROGMEMprint(statusMessage6); 
                    Serial.println(door2Locked); 
                    break;
                   }
                           
                 case 'o': {  
                  if(privmodeEnabled==true) {
                    if(atoi(cmdString[1]) == 1)
                    {                                     
                      alarmState(0);                                       // Set to door chime only/open doors                                                                       
                      armAlarm(4);
                      doorUnlock(1);                                       // Open the door specified
                      door1locktimer=millis();
                      break;
                    }                    
                   if(atoi(cmdString[1]) == 2)
                   {  
                      alarmState(0);                                       // Set to door chime only/open doors                                                                       
                      armAlarm(4);
                      doorUnlock(2);                                        
                      door2locktimer=millis();
                      break;               
                    }
                    Serial.print("Invalid door number!");
                 }

                   else{logprivFail();}
                    break;
                  } 

                   case 'r': {                                                 // Remove a user
                  if(privmodeEnabled==true) {
                    dumpUser(atoi(cmdString[1]));   //cmdString[1] is the user number
                    deleteUser(atoi(cmdString[1]));
                   }
                  else{logprivFail();}
                    break; 
                   }              

                   case 'R':{        // Remove a userI3D 
                  if(privmodeEnabled==true) {
                    dumpUserI3D(atoi(cmdString[1]));
                    deleteUserI3D(atoi(cmdString[1]));
                   }
                  else{logprivFail();}
                    break; 
                   }

                   case 'm': {                                                                // Add/change a user                   
                 if(privmodeEnabled==true) {
                   Serial.println("inside m(odify)") ;
                   dumpUser(atoi(cmdString[1]));
                   addUser(atoi(cmdString[1]), atoi(cmdString[2]), strtoul(cmdString[3],NULL,16));                
                   dumpUser(atoi(cmdString[1]));
                  }
                 else{logprivFail();}                                    
                             
                    break;
                  }
                     
                     //added by ML as part of tailoring to add specific info
                     //    for each user   ; not necessarly used    
                     //    One possible way to use:  cmdString[1] is number of the user (1 byte)
                     // NOTE: THE USER NUMBER WOULD BE THE SAME NUMBER AS IN THE CRM for the USER
                     //                              cmdString[2] is a user mask (1 byte)
                     //                              cmdString[3] the rfid tag (4 bytes)
                     //
                     case 'p': {
                  Serial.println("inside p -- may be used instead of the m switch") ;
                  //Serial.println(cmdString[0]) ;
                  //Serial.println(cmdString[1]) ;
                  //Serial.println(atoi(cmdString[2])) ;
                  //byte theusersmask = atoi(cmdString[2]) ;
                  //Serial.println(theusersmask) ;
                  Serial.println(strtoul(cmdString[3], NULL,16)) ;
                  Serial.println(strtoul(cmdString[4],NULL, 16)) ;
                  //is the user there already and may be modified if so
                  dumpUserI3D(atoi(cmdString[1]));
                  //add the user -- or modify the user's data (eg new rfid tag or user mask assignment if used
                  addUserI3D(atoi(cmdString[1]), atoi(cmdString[2]), strtoul(cmdString[3],NULL,16), strtoul(cmdString[4],NULL,16)); 
                 //show the user  details again               
                  dumpUserI3D(atoi(cmdString[1]));
                       break ;
                     }        
                             
                  case '?': {                                                  // Display help menu
                     PROGMEMprintln(consolehelpMessage1);
                     PROGMEMprintln(consolehelpMessage2);
                     PROGMEMprintln(consolehelpMessage3);
                     PROGMEMprintln(consolehelpMessage4);
                     PROGMEMprintln(consolehelpMessage5);                  
                     PROGMEMprintln(consolehelpMessage6);   
                      PROGMEMprintln(consolehelpMessage7I3D) ;
                      PROGMEMprintln(consolehelpMessage8I3D) ;
                     
                   break;
                            }

                   default:  
                    PROGMEMprintln(consoledefaultMessage);
                    break ;
          }  
  }               
}                                      // End of function 




/* Wrapper functions for interrupt attachment
 Could be cleaned up in library?
 */
void callReader1Zero(){wiegand26.reader1Zero();}
void callReader1One(){wiegand26.reader1One();}
void callReader2Zero(){wiegand26.reader2Zero();}
void callReader2One(){wiegand26.reader2One();}
void callReader3Zero(){wiegand26.reader3Zero();}
void callReader3One(){wiegand26.reader3One();}


